package com.example.redissondemo.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.SentinelServersConfig;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.util.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.List;

/**
 * @author Liunh
 */
@Slf4j
@Configuration
public class RedissonConfig {

    private final RedisProperties redisProperties;
    private final RedisProperties.Cluster cluster;
    @Value(value = "${spring.redis.redisson.clusterServersConfig.connectTimeout:10000}")
    private int timeout;
    @Value(value = "${spring.redis.redisson.clusterServersConfig.masterConnectionPoolSize:64}")
    private int connectionPoolSize;
    @Value(value = "${spring.redis.redisson.clusterServersConfig.masterConnectionMinimumIdleSize:12}")
    private int connectionMinimumIdleSize;
    @Value(value = "${spring.redis.redisson.clusterServersConfig.subscriptionsPerConnection:5}")
    private int subscriptionsPerConnection;
    @Value(value = "${spring.redis.redisson.clusterServersConfig.subscriptionConnectionPoolSize:10}")
    private int subscriptionConnectionPoolSize;
    @Value(value = "${spring.redis.redisson.clusterServersConfig.pingConnectionInterval:60000}")
    private int pingConnectionInterval;
    private static String REDIS_ADDRESS_PREFIX = "redis://";
    private final String PASSWORD_ENCRYPT_KEY = "Abcde@0123456789";

    public RedissonConfig(RedisProperties redisProperties, RedisProperties.Cluster cluster) {
        this.redisProperties = redisProperties;
        this.cluster = cluster;
    }

    @Primary
    @Bean(name = "redissonClient", destroyMethod="shutdown")
    public RedissonClient redissonClient() {
        this.timeout = this.timeout < 5000 ? 900000 : this.timeout;
        this.subscriptionConnectionPoolSize = this.subscriptionConnectionPoolSize < 6 ? 300 : this.subscriptionConnectionPoolSize;
        this.subscriptionsPerConnection = this.subscriptionsPerConnection < 11 ? 6000 : this.subscriptionsPerConnection;
        if ("SINGLE".equalsIgnoreCase(redisProperties.getModel())) {
            return this.redissonSingle();
        }
        return this.redissonCluster();
    }

    @Bean(destroyMethod = "shutdown")
    public RedissonClient shutdown(@Qualifier("redissonClient") RedissonClient redissonClient) {
        return redissonClient;
    }

    /**
     * 单机模式
     */
    private RedissonClient redissonSingle() {
        log.info(">>>>>> standalone mode, RedissonClient timeout = {}, subscriptionConnectionPoolSize = {}, subscriptionsPerConnection = {}", timeout, subscriptionConnectionPoolSize, subscriptionsPerConnection);
        final Config config = new Config();
        final JsonJacksonCodec codec = new JsonJacksonCodec();
        config.setCodec(codec);
        final SingleServerConfig serverConfig = config.useSingleServer()
                .setAddress(REDIS_ADDRESS_PREFIX + redisProperties.getHost() + ":" + redisProperties.getPort())
                .setTimeout(timeout)
                .setPingConnectionInterval(this.pingConnectionInterval)
                .setConnectionPoolSize(this.connectionPoolSize)
                .setConnectionMinimumIdleSize(this.connectionMinimumIdleSize)
                .setSubscriptionConnectionPoolSize(this.subscriptionConnectionPoolSize)
                .setSubscriptionsPerConnection(this.subscriptionsPerConnection);
        if (StringUtils.hasText(redisProperties.getPassword())) {
            String password = redisProperties.isEncryptPassword() ? this.decryptAesByEcb(PASSWORD_ENCRYPT_KEY, redisProperties.getPassword()) : redisProperties.getPassword();
            serverConfig.setPassword(password);
        }
        return Redisson.create(config);
    }

    /**
     * 集群模式
     */
    private RedissonClient redissonCluster() {
        log.info(">>>>>> Cluster mode, RedissonClient timeout = {}, subscriptionConnectionPoolSize = {}, subscriptionsPerConnection = {}", timeout, subscriptionConnectionPoolSize, subscriptionsPerConnection);
        final List<String> nodes = cluster.getNodes();
        Config config = new Config();
        final JsonJacksonCodec codec = new JsonJacksonCodec();
        config.setCodec(codec);
        ClusterServersConfig clusterServersConfig = config.useClusterServers();
        clusterServersConfig.setScanInterval(3000);
        clusterServersConfig.setSubscriptionConnectionPoolSize(this.subscriptionConnectionPoolSize);
        clusterServersConfig.setSubscriptionsPerConnection(this.subscriptionsPerConnection);
        if (StringUtils.hasText(redisProperties.getPassword())) {
            String password = redisProperties.isEncryptPassword() ? this.decryptAesByEcb(PASSWORD_ENCRYPT_KEY, redisProperties.getPassword()) : redisProperties.getPassword();
            clusterServersConfig.setPassword(password);
        }
        for (String node : nodes) {
            clusterServersConfig.addNodeAddress(REDIS_ADDRESS_PREFIX + node);
        }
        return Redisson.create(config);
    }

    /**
     * 哨兵模式
     */
    private RedissonClient redissonSentinel() {
        log.info(">>>>>> Sentinel mode, RedissonClient timeout = {}, subscriptionConnectionPoolSize = {}, subscriptionsPerConnection = {}", timeout, subscriptionConnectionPoolSize, subscriptionsPerConnection);
        String masterName = redisProperties.getSentinel().getMaster();
        String nodes = redisProperties.getSentinel().getNodes();
        final Config config = new Config();
        final JsonJacksonCodec codec = new JsonJacksonCodec();
        config.setCodec(codec);
        final SentinelServersConfig sentinelServersConfig = config.useSentinelServers();
        sentinelServersConfig.setScanInterval(3000);
        sentinelServersConfig.setSubscriptionConnectionPoolSize(this.subscriptionConnectionPoolSize);
        sentinelServersConfig.setSubscriptionsPerConnection(this.subscriptionsPerConnection);
        if (StringUtils.hasText(redisProperties.getPassword())) {
            String password = redisProperties.isEncryptPassword() ? this.decryptAesByEcb(PASSWORD_ENCRYPT_KEY, redisProperties.getPassword()) : redisProperties.getPassword();
            sentinelServersConfig.setPassword(password);
        }
        sentinelServersConfig.setMasterName(masterName);
        final String[] nodeArr = StringUtils.split(nodes, ",");
        for (int i = 0; i < nodeArr.length; i++) {
            nodeArr[i] = REDIS_ADDRESS_PREFIX + nodeArr[i];
        }
        sentinelServersConfig.addSentinelAddress(nodeArr);
        return Redisson.create(config);
    }

    /**
     * AES加密/ECB工作模式
     *
     * @param key  密钥 初始长度为24 且必须为8的整数倍
     * @param data 待加密数据
     * @return
     * @throws Exception
     */
    private String encryptAesByEcb(String key, String data) {
        if (!StringUtils.hasText(data)) {
            return "";
        }
        try {
            //获取密钥
            final Key secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
            //根据转化规则获取密码对象
            final Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            //初始化
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            //加密
            byte[] bytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return Base64.encodeBase64String(bytes);
        } catch (Exception e) {
            log.error("AES加密数据异常！", e);
        }
        return "";
    }

    /**
     * AES解密/ECB工作模式
     *
     * @param key  密钥 初始长度为24 且必须为8的整数倍
     * @param data 待解密数据
     * @return
     * @throws Exception
     */
    private String decryptAesByEcb(String key, String data) {
        if (!StringUtils.hasText(data)) {
            return "";
        }
        try {
            //获取密钥
            final Key secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
            //根据转化规则获取密码对象
            final Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            //初始化
            cipher.init(Cipher.DECRYPT_MODE, secretKey);
            //加密
            byte[] bytes = cipher.doFinal(Base64.decodeBase64(data));
            return new String(bytes);
        } catch (Exception e) {
            log.error("AES解密数据异常！", e);
        }
        return "";
    }

}
